package game.controller;

import game.packet.GameAddUnitsPacket;
import lobby.controller.User;
import lobby.controller.Nation;
import game.client.GameClientThread;
import game.client.AddUnitsResponseThread;
import game.util.GameLogger;
import game.util.EventLogger;
import game.controller.Region.LandRegion;
import game.controller.Region.Region;
import game.controller.Region.Port;
import game.controller.Unit.MilitaryUnit;
import util.Utilities;

import java.util.*;

import shared.controller.LobbyConstants;

/**
 * GameController.java  Date Created: Oct 24, 2012
 *
 * Purpose: To control the game.
 *
 * Description:
 *
 * @author Chrisb
 */
public class GameController {

    public GameController(User user, byte[] gameId) {
        isGameReady = false;
        this.user = user;
        this.gameId = gameId;
        chatNation = Nation.GLOBAL;

        gct = new GameClientThread(Utilities.idAsInetAddress(gameId), this);
        gct.setDaemon(true);
        gct.start();

        addUnitsRT = new AddUnitsResponseThread(Utilities.idAsInetAddress(gameId), gameId, this.user.getNation(), this);
//        addUnitsRT = new AddUnitsResponseThread(Utilities.idAsInetAddress(gameId), gameId, this.user.getSecondaryNation(), this);
    }

    public void requestGameInfo() {
        gct.requestGameInfo(gameId, getUserNation());
        gct.requestGameUsers(gameId, getUserNation());
    }

    /**
     * createGame is used to initialize the game object before setting up the gui.
     * @param gameId - unique id of the game, used by server to know which game to send packets to.
     * @param options - the game options.
     * @param msd - the game mode, scenario and duration.
     */
    public void createGame(byte[] gameId, boolean[] options, int[] msd, int nation) {
        if (nation == getUserNation()) {
            System.out.println("***** GameController:createGame");
            if (Utilities.compareLobbyIds(this.gameId,gameId))
                game = new GameInstance(gameId, options, msd);
    //            unitController = new UnitController(this);
            else
                GameLogger.log("GameController:createGame - lobbyIds do not match");
        }
    }

    /**
     * addUsers is used to initialize the users in the game befroe setting up the gui.
     * @param gameId - unique id of the game, used by server to know which game to send packets to.
     * @param users - list of users in the game.
     */
    public void addUsers(byte[] gameId, ArrayList<User> users) {
        System.out.println("GameController:addUsers");
        if (game != null) {
            System.out.println("GameController:addUsers game not null");
            if (Utilities.compareLobbyIds(game.getGameId(), gameId)) {
                System.out.println("GameController:addUsers add users.." + users.size());
                game.addUsers(users);
                System.out.println("GameController:addUsers final");
            } else {
                GameLogger.log("GameController:addUsers - lobbyIds do not match");
            }
        }
    }

    /**
     * Change the nation with which this.nation will send/view messages.
     * @param newNation - the new nation, this.nation wishes to send/view messages.
     */
    public void changeChatNation(int newNation) {
        if (newNation == Nation.GLOBAL) {
            display.changeChatNation(chatNation, newNation);
            chatNation = newNation;
        } else if (game.isPlayerNation(newNation)) {
            display.changeChatNation(chatNation, newNation);
            chatNation = newNation;
        }
    }

    /**
     * Send a chat message to the server.  Include the gameId which it came from,
     * the nation who is sending the message, the nation(s) which the message will be sent
     * and the message the user is sending.
     * @param message - the communique.
     */
    public void sendChatMessage(String message) {
        gct.sendChatMessage(gameId, user.getNation(), getChatNation(),  message);
    }

    /**
     * Receive a communique from the server.  Depending on who it was sent to/from add the message
     * to the appropriate chat window.
     * @param gameId - id of game which message was sent from.
     * @param fromNation - the nation who sent the message.
     * @param toNation - the nation the message was sent to.
     * @param message - the communique
     */
    public void receiveChatMessage(byte[] gameId, int fromNation, int toNation, String message) {
        if (Utilities.compareLobbyIds(gameId, this.gameId)) {
            String fromLeaderName = "ErrorName";
            if (toNation == getUserNation()) {
                for (User u : game.getUsers())
                    if (u.getNation() == fromNation)
                        fromLeaderName = u.getLeader();
                display.receiveChatMsg(fromNation, fromLeaderName, message);
            } else if (fromNation == getUserNation()) {
                for (User u: game.getUsers())
                    if (u.getNation() == fromNation)
                        fromLeaderName = u.getLeader();
                display.addChatMsg(toNation, fromLeaderName, message);            
            } else if (toNation == Nation.GLOBAL) {
                for (User u: game.getUsers())
                    if (u.getNation() == fromNation)
                        fromLeaderName = u.getLeader();
                if (getChatNation() == Nation.GLOBAL)
                    display.addChatMsg(toNation, fromLeaderName, message);
                else
                    display.receiveChatMsg(toNation, fromLeaderName, message);
            }
        }
    }

    /*
     * Political Actions
     */
    public boolean canDeclareWar(int declaringNation, int enemyNation) {
        //Check Game Options
        //Check Paps
            //2 Paps unless an ally is at war with enemyNation, then 1 Pap.
        //Check political status (can not be allied, at war, right of passage, grace, reverse grace)
        //declaringNation and enemyNation can not be controlled by the same user.
        //Team game: can't declare on teammates


        return false;
    }

    public void declareWar(int declaringNation, int enemyNation) {
        //canDeclareWar()
            //Check Paps
                //Remove Paps
            //Check political status
                //If declaringNation had rightsOfPassage from enemyNation, remove them
                //If declaringNation had gracePeriod(s), remove them
            //Add declaringNation to enemyNations atWar list
            //Add enemyNation to declaringNations atWar list
            //Tell Server all this information
    }

    public boolean canSueForPeace(int sueingNation) {
        //Check if sueingNation is at war.
        //Check Paps >= 3;

        return false;
    }

    public void sueForPeace(int sueingNation) {
        //This starts a huge long process..
    }

    public boolean canConcludeArmistice(int nationOne, int nationTwo) {
        //Check if nationOne is at war with nationTwo
        //Check paps >= 1 for both nationOne and nationTwo

        return false;
    }

    public void concludeArmistice(int nationOne, int nationTwo) {
        //canConcludeArmistice()
            //Remove 1 pap from each nation.
            //Remove nationOne from nationTwo's atWar list
            //Remove nationTwo from nationOne's atWar list
            //Tell the server
    }

    public boolean canFormAlliance(int nationOne, int nationTwo) {
        //Check Paps >= 1 for each nation
        //Check political status
            //reverse grace periods
            //alliances
            //wars
        //Check team game

        return false;
    }

    public void formAlliance(int nationOne, int nationTwo) {
        //canFormAlliance()
            //remove one pap from each nation
            //add nationOne to nationTwo allianceList
            //add nationTwo to nationOne allianceList
            //Tell the server
    }

    public boolean canBreakAlliance(int breakingNation, int alliedNation) {
        //Check if Team game
        //Check if breakingNation is on alliedNation's allianceList
        //Check paps >= 1 for breakingNation

        return false;
    }

    public void breakAlliance(int breakingNation, int alliedNation) {
        //Remove one pap from breakingNation
        //Remove alliedNation from breakingNation's allianceList
        //Remove breakingNation from alliedNation's allianceList
        //Tell Server
    }

    public boolean canRecruitMinorNation(int majorNation, int minorNation) {
        //Check minorNation is uncontrolled
        //Check minorNation not Gibraltar
        //Check paps >= 2 for majorNation

        return false;
    }

    public void attemptRecruitMinorNation(int majorNation, int minorNation) {
        //canRecruitMinorNation()
            //Remove 2 paps from majorNation
            //send request to server to attempt recruit
    }

    public boolean canAnnexMinorNation(int majorNation, int minorNation) {
        //Check minorNation is uncontrolled
        //Check paps >= 1 for majorNation
        //Check majorNation has units in minorNation

        return false;
    }

    public void attemptAnnexMinorNation(int majorNation, int minorNation) {
        //canAnnexMinorNation()
            //Remove 1 pap from majorNation
            //send request to server to attempt annexation
    }

    public boolean canRestoreRegion(int majorNation, int region) {
        //Check region is controlled by a major nation
        //Check region is not native-owned homeland region
        //Check paps >= 1 for majorNation

        return false;
    }

    public void attemptRestoreRegion(int majorNation , int region) {
        //canRestoreRegion()
            //Remove 1 pap from majorNation
            //send request to server to attempt restoration
    }

    public boolean canAbandonRegion(int majorNation, int region) {
        //Check is majorNation controls region
        //Check if majorNation has required PAPs (1 is pc, 2 if npc)

        return false;
    }

    public void abandonRegion(int majorNation, int region) {
        //canAbandonRegion()
            //Tell server
    }

    public boolean canFomentUprising(int majorNation, int region) {
        //Check paps >= 1 for majorNation
        //Check region is not in uprising
        //Check if region is legal (Ireland, Vendee, Don Basin, Arabia, foreign-owned homeland, non-homeland region (other than Gibraltar)
        //Can be done in secret.. PAPs are not public knowledge..
        return false;
    }

    public void fomentUprising(int majorNation, int region) {
        //canFomentUprising()
            //Tell Server
    }

    public boolean canSuppressUprising(int majorNation, int region) {
        //Check region is in uprising
        //Are at least two (2) units in region

        return false;
    }

    public void attemptSuppressUprising(int majorNation, int region) {
        //canSuppressUprising()
            //Send request to server to attempt suppress uprising
    }

    public boolean canGrantRightOfPassage(int grantingNation, int receivingNation) {
        //Check grantingNation and receivingNation are neutral
        //Check reverse grace period

        return false;
    }

    public void grantRightOfPassage(int grantingNation, int receivingNation) {
        //canGrantRightOfPassage()
            //Tell Server
    }

    public boolean canRescindRightOfPassage(int rescindingNation, int receivingNation) {
        //Were rights of passage granted voluntarily

        return false;
    }

    public void rescindRightOfPassage(int rescindingNation, int receivingNation) {
        //canRescindRightOfPassage()
            //Tell Server
    }

    public boolean canEnforceContinentalSystem(int majorNation) {
        //Is majorNation France
        //Is start of Production round
        //Check paps >= 1 for majorNation
        //Is Britain only major nation at war with France
        //Has enforced < 2 for this production round

        return false;
    }

    public void enforceContinentalSystem(int majorNation) {
        //canEnforceContinentalSystem()
            //Tell Server
    }

    public boolean canControlNPN(int majorNation, int nonPlayerNation) {
        //Check paps >= 2 (or more) for majorNation
        //Check nonPlayerNation is not a player nation

        return false;
    }

    public void controlNPN(int majorNation, int nonPlayerNation) {
        //canControlNPN()
            //Send request to server to attempt control non-player nation
    }
    /*
     * END Political Actions
     */

    public boolean isTurnToPlaceUnits() { return game.isBlindSetup() || (game.getTurn() == getUserNation()); }

    public ArrayList<MilitaryUnit> getUnplacedUnits() {
        ArrayList<MilitaryUnit> unplacedUnits = new ArrayList<MilitaryUnit>();

        for (MilitaryUnit u : game.getNation(getUserNation()).getMilitary()) {
            if (!u.isPlacedOnMap())
                unplacedUnits.add(u);
        }
        return unplacedUnits;
    }

    public void finalizeUnitPlacement(HashMap<Region, ArrayList<MilitaryUnit>> placementMap) {
        processUnitPlacement(placementMap);
    }

    public void processUnitPlacement(GameAddUnitsPacket aup) {
        if (aup.getOwningNation() != user.getNation()) {
            if (!game.isBlindSetup()) {
                System.out.println("!owningNation place units " + user.getNation() + " != " + aup.getOwningNation());
                boolean unitsAdded = placeUnits(aup);
                if (!unitsAdded) {
                    System.err.println("processUnitPlacement(aup) - Failed to add units");
                    GameLogger.log("processUnitPlacement(aup) - Failed to add units");
                } else
                    display.refreshTerritoryInfo();
            } else {
                //If blind setup store the AUP until all nations have placed units.
                nationsAUP.add(aup);
            }
        } else {
            System.out.println("owningNation do not place units " + user.getNation() + " == " + aup.getOwningNation());
        }
    }

    public void processUnitPlacement(HashMap<Region, ArrayList<MilitaryUnit>> placementMap) {
        boolean unitsAdded;
        addUnitsRT.clearAUPs();
        for (Region region : placementMap.keySet() ) {
            unitsAdded = placeUnits(region.toString(), placementMap.get(region));
            if (unitsAdded) {
                addUnitsRT.addPacket(gameId, game.getMap().getRegionIndex(region), region.getType(), placementMap.get(region));
            } else {
                GameLogger.log("Failed to add units to the following region, " + region.toString() + ", num of units (" + placementMap.get(region).size() + ")");
                System.err.println("Failed to add units to the following region, " + region.toString() + ", num of units (" + placementMap.get(region).size() + ")");
            }
        }
        display.refreshTerritoryInfo();
        addUnitsRT.sendPackets();
    }

    private boolean placeUnits(GameAddUnitsPacket aup) {
        Region region = game.getMap().getRegionFromIndex(aup.getRegionIndex(), aup.getRegionType());
        return placeUnits(region.toString(), Utilities.getUnitsFromPacket(aup));
    }

    private boolean placeUnits(String regionName, ArrayList<MilitaryUnit> units) {
//        unitController.addUnits(units);
        return game.addUnitsToRegion(regionName, units);
    }

    /*
     * When blind setup is on this method will be called once all nations have placed units.
     */
    private void blindSetupPlaceUnits() {
        for (GameAddUnitsPacket aup : nationsAUP) {
            System.out.println("GameController - blindSetup packets");
            if (!placeUnits(aup)) {
                System.err.println("blindSetupPlaceUnits - failed to add units; nation: " + aup.getOwningNation()  + " regionType: " + aup.getRegionType() + " regionIndex " + aup.getRegionIndex());
                GameLogger.log("blindSetupPlaceUnits - failed to add units; nation: " + aup.getOwningNation()  + " regionType: " + aup.getRegionType() + " regionIndex " + aup.getRegionIndex());
            }
        }
        nationsAUP.clear();
        nationsPlaced.clear();
    }

    /*
     * Add nation to the list of nations who have placed units.  Only really needed for blind setup.
     * Then check if all nations have placed, if so reset nationPlaced and add all units for all nations
     * to the clients version of the map.
     */
    public void allUnitsAdded(int nation) {
        System.out.println("GameController.allUnitsAdded: " + nation + " nationsPlaced.size(): " + nationsPlaced.size());
        nationsPlaced.add(nation);
        if (!game.isBlindSetup())
            game.nextTurn();
        System.out.println("GameController.allUnitsAdded: nationsPlaced.size(): " + nationsPlaced.size() + " game.getUsers().size(): " + game.getUsers().size());
        if (nationsPlaced.size() == game.getUsers().size()) {
            System.out.println("GameController - nationsPlaced.size() == game.getUsers().size()");
            boolean allNationsReady = true;
            //Should be one integer for each nation, if not probably an error.
            for (User u : game.getUsers()) {
                if ( !nationsPlaced.contains(u.getNation()) ) {
                    try {
                        allNationsReady = false;
                        throw new Exception();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.err.println("GameController.allUnitsAdded:  Something went wrong adding units - userSize: " + game.getUsers().size() + " nationPlaced.size(): " + nationsPlaced.size());
                        GameLogger.log("GameController.allUnitsAdded - userSize: " + game.getUsers().size() + " nationPlaced.size(): " + nationsPlaced.size() + " Msg: "  + e.getMessage());
                    }
                }
            }
            if ( allNationsReady ) {
                System.out.println("GameController - allNationsReady");
                //todo take it from here
                //START THE GAME!!!!!!!
                blindSetupPlaceUnits();
                display.refreshTerritoryInfo();
            }
        }
    }

    public ArrayList<MilitaryUnit> getUnitsInRegion(String regionName) {
        return game.getMap().getRegion(regionName).getOccupyingUnits();
    }

    public Region getRegion(String regionName) {
        return game.getMap().getRegion(regionName);
    }

    public ArrayList<LandRegion> getUserHomelands() {
        return game.getNation(getUserNation()).getHomelands();
    }

    public ArrayList<LandRegion> getUserTerritory() {
        return game.getNation(getUserNation()).getTerritory();
    }

    public ArrayList<Region> getUserHomelandPorts() {
        return game.getNation(getUserNation()).getHomelandPorts();
    }

    public ArrayList<Region> getAllRegions() {
        return game.getMap().getAllRegions();
    }

    /**
     * If first round, can place in all regions owned by nations (S.X only), otherwise only homelands.
     * All homeland ports that have not built a ship in last three (3) production rounds.
     * No units may be placed in regions occupied by enemy troops or that is in uprising.
     * Regions must be under native control.
     * @return an ArrayList of the regions the userNation can place units in.
     */
    public ArrayList<Region> getUserPlaceableRegions() {
        ArrayList<Region> placeableRegions = new ArrayList<Region>();
        //Land Regions
        if (game.isFirstRound() && game.getGameScenario() == LobbyConstants.X)
            placeableRegions.addAll(getUserTerritory());
        else {
            for (LandRegion r : getUserHomelands())
                if ( r.getControllingNation() == getUserNation()
                        && !r.isUprising()
                        && !r.isEnemyOccupied() )
                    placeableRegions.add(r);
        }
        //Ports
        for (Region p : getUserHomelandPorts()) {
            LandRegion region = ((Port)p).getPortRegion();
            if ( region.getControllingNation() == getUserNation()
                    && !region.isUprising() && !region.isEnemyOccupied()) {
                placeableRegions.add(p);
            }
        }
        return placeableRegions;
    }

    /*
     * When word is recieved from server, flag the game as ready and set up the game for first turn placement.
     */
    public void makeGameReady(byte[] gameId, int nation) {
        if (nation == getUserNation() && Utilities.compareLobbyIds(this.gameId,gameId) ) {
            isGameReady = true;
            EventLogger.createEventFile(gameId);
            nationsPlaced = Collections.synchronizedSet( new HashSet<Integer>(game.getUsers().size()) );
        }
    }

    public void init() { game.setupGame(); }
    public int getTurn() { return game.getTurn(); }
    public String getRoundYear() { return game.getRoundYearAsString(); }
    public String getProductionRound() { return game.getProductionRoundAsString(); }

    public void setDisplayController(DisplayController display) { this.display = display; }
    public DisplayController getDisplay() { return display; }

    public boolean isGameReady() { return isGameReady; }

    public boolean isPlayerNation(int nation) { return game.isPlayerNation(nation); }
    public NationInstance getNationInstance(int nation) { return game.getNation(nation); }
    public int getGameMode() { return game.getGameMode(); }
    public int getGameScenario() { return game.getGameScenario(); }
    public int getGameDuration() { return game.getGameDuration(); }

    public int getUserNation() { return user.getNation(); }
    public String getUserLeader() { return user.getLeader(); }
    public String getUserName() { return user.getUserName(); }

    public int getChatNation() { return chatNation; }

    private User user;
    private byte[] gameId;
    private GameInstance game;
//    private UnitController unitController;
    private GameClientThread gct;
    private DisplayController display;
    private int chatNation;
    private boolean isGameReady;
    private Set<Integer> nationsPlaced;
    private List<GameAddUnitsPacket> nationsAUP = new ArrayList<GameAddUnitsPacket>();

    //Response Thread Team
    private AddUnitsResponseThread addUnitsRT;

    public static final int MAX_CHAT_LENGTH = 250;

    /**
     * Grace Period - set in sue for peace
     *
     * France sues vs Prussia & Russia
     *
     * France (grace from) Prussia, Russia
     * Prussia (reverse grace) France
     * Russia (reverse grace) France
     */
}